import cv2
import numpy as np
import pyautogui
import time
import win32gui
import win32con
import win32process
import win32api
import win32ui
import ctypes
from ctypes import wintypes
import psutil
import keyboard
import threading
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
import sys
from PIL import Image, ImageTk, ImageDraw, ImageGrab
import json  # 添加json导入
import shutil
import subprocess

# 定义SendInput结构和相关常量
LONG = ctypes.c_long
DWORD = ctypes.c_ulong
ULONG_PTR = ctypes.POINTER(DWORD)
WORD = ctypes.c_ushort

# 修复ULONG_PTR定义
class _POINTER_X64(ctypes.Structure):
    _fields_ = [("dummy", ctypes.c_uint64)]

class _POINTER_X86(ctypes.Structure):
    _fields_ = [("dummy", ctypes.c_uint32)]

if ctypes.sizeof(ctypes.c_void_p) == ctypes.sizeof(ctypes.c_uint64):
    ULONG_PTR = ctypes.c_uint64
else:
    ULONG_PTR = ctypes.c_uint32

# 鼠标输入类型
INPUT_MOUSE = 0
INPUT_KEYBOARD = 1

# 鼠标事件标志
MOUSEEVENTF_LEFTDOWN = 0x0002
MOUSEEVENTF_LEFTUP = 0x0004
MOUSEEVENTF_ABSOLUTE = 0x8000
MOUSEEVENTF_MOVE = 0x0001

class MOUSEINPUT(ctypes.Structure):
    _fields_ = [
        ("dx", LONG),
        ("dy", LONG),
        ("mouseData", DWORD),
        ("dwFlags", DWORD),
        ("time", DWORD),
        ("dwExtraInfo", ULONG_PTR)
    ]

class INPUTUNION(ctypes.Union):
    _fields_ = [
        ("mi", MOUSEINPUT),
        # 可以添加其他输入类型如键盘等
    ]

class INPUT(ctypes.Structure):
    _fields_ = [
        ("type", DWORD),
        ("u", INPUTUNION)
    ]

# 导入SendInput函数
SendInput = ctypes.windll.user32.SendInput
SendInput.argtypes = (wintypes.UINT, ctypes.POINTER(INPUT), wintypes.INT)
SendInput.restype = wintypes.UINT

# 添加全局变量控制程序运行状态
running = False
selected_hwnd = None
template_path = "dian.png"
template_paths = ["dian.png", "dian2.png", "dian3.png", "dian4.png"]  # 按优先级排序的模板列表
click_thread = None
debug_mode = False
click_method = "sendinput"  # 'pyautogui', 'api', 'post_message', 'sendinput', 'force'
screen_width, screen_height = pyautogui.size()  # 获取屏幕尺寸
log_update_count = 0  # 日志更新计数器
last_successful_template = None  # 记录上次成功的模板

# 修改初始化代码，确保template_paths使用绝对路径
# 全局变量
root_path = os.path.dirname(os.path.abspath(__file__))
templates_dir = os.path.join(root_path, "templates")
os.makedirs(templates_dir, exist_ok=True)  # 确保模板目录存在

template_path = os.path.join(root_path, "dian.png")
template_paths = [
    os.path.join(root_path, "dian.png"), 
    os.path.join(root_path, "dian2.png"), 
    os.path.join(root_path, "dian3.png"), 
    os.path.join(root_path, "dian4.png")
]

def is_admin():
    """检查程序是否拥有管理员权限"""
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False

def request_admin():
    """请求管理员权限重启程序"""
    if not is_admin():
        # 重新以管理员权限启动程序
        script = os.path.abspath(sys.argv[0])
        params = ' '.join(sys.argv[1:])
        try:
            # 以管理员权限启动
            ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, script + ' ' + params, None, 1)
            # 退出当前实例
            sys.exit(0)
        except:
            messagebox.showerror("错误", "无法获取管理员权限，某些功能可能无法正常工作")

def get_process_windows():
    """
    获取所有进程窗口信息
    返回格式: [(hwnd, title, pid, process_name), ...]
    """
    windows_info = []
    
    def callback(hwnd, results):
        if win32gui.IsWindowVisible(hwnd) and win32gui.IsWindowEnabled(hwnd):
            window_title = win32gui.GetWindowText(hwnd)
            if window_title:  # 只收集有标题的窗口
                try:
                    # 获取窗口关联的进程ID
                    _, pid = win32process.GetWindowThreadProcessId(hwnd)
                    try:
                        process = psutil.Process(pid)
                        process_name = process.name()
                        results.append((hwnd, window_title, pid, process_name))
                    except (psutil.NoSuchProcess, psutil.AccessDenied):
                        pass
                except:
                    pass
        return True
    
    windows = []
    win32gui.EnumWindows(callback, windows)
    
    # 按进程名称和窗口标题排序
    return sorted(windows, key=lambda x: (x[3], x[1]))

def focus_window(hwnd):
    """
    让指定的窗口成为前台窗口
    """
    if hwnd:
        # 确保窗口未最小化
        if win32gui.IsIconic(hwnd):
            win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
        
        # 将窗口置于前台
        win32gui.SetForegroundWindow(hwnd)
        
        # 给窗口获取焦点一些时间
        time.sleep(0.5)
        return True
    return False

def get_window_rect(hwnd):
    """获取窗口的位置和大小"""
    if hwnd:
        try:
            left, top, right, bottom = win32gui.GetWindowRect(hwnd)
            return left, top, right - left, bottom - top  # x, y, width, height
        except:
            pass
    return None

def find_snowflake_buttons(screenshot, template_path, threshold=0.8):
    """
    在截图中查找所有与模板匹配的按钮
    
    Args:
        screenshot: 游戏窗口的截图
        template_path: 按钮模板图片的路径
        threshold: 匹配阈值，值越高要求匹配越精确
        
    Returns:
        按钮中心点坐标列表 [(x, y), ...]
    """
    # 读取模板图像
    template = cv2.imread(template_path, cv2.IMREAD_UNCHANGED)
    
    # 确保模板图像已正确加载
    if template is None:
        return []
    
    # 转换图像为RGB格式以便匹配
    if template.shape[2] == 4:  # 带有Alpha通道
        template = cv2.cvtColor(template, cv2.COLOR_BGRA2BGR)
    
    # 将截图转换为OpenCV格式 - 使用更高效的方法
    screenshot_cv = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)
    
    # 缩放优化 - 如果模板和截图都很大，可以减小尺寸加快匹配速度
    scale_factor = 1.0  # 默认不缩放
    if screenshot_cv.shape[0] > 1000 or screenshot_cv.shape[1] > 1000:
        scale_factor = 0.8
        screenshot_cv = cv2.resize(screenshot_cv, (0, 0), fx=scale_factor, fy=scale_factor)
        template = cv2.resize(template, (0, 0), fx=scale_factor, fy=scale_factor)
    
    # 执行模板匹配
    result = cv2.matchTemplate(screenshot_cv, template, cv2.TM_CCOEFF_NORMED)
    
    # 查找匹配点 - 使用更高效的方法
    locations = np.where(result >= threshold)
    button_centers = []
    
    # 模板尺寸
    template_width, template_height = template.shape[1], template.shape[0]
    
    # 处理匹配结果，合并重叠的检测结果
    points = list(zip(*locations[::-1]))  # (x, y) 坐标
    if not points:
        return []
    
    # 只有在调试模式下才输出匹配点数量，减少不必要的UI更新
    if debug_mode:
        log_text.insert(tk.END, f"发现 {len(points)} 个潜在匹配，阈值: {threshold}\n")
        log_text.see(tk.END)
    
    # 使用更高效的非极大值抑制方法
    # 首先对矩形和分数进行排序
    rect_list = []
    score_list = []
    for pt in points:
        rect = (pt[0], pt[1], pt[0] + template_width, pt[1] + template_height)
        rect_list.append(rect)
        score_list.append(result[pt[1], pt[0]])
    
    # 使用更快的非极大值抑制算法
    if len(rect_list) > 100:  # 如果匹配点太多，使用更简单的方法
        # 按分数排序并取前20个点
        sorted_indices = np.argsort(score_list)[::-1][:20]
        for idx in sorted_indices:
            x, y, x2, y2 = rect_list[idx]
            center_x = x + template_width // 2
            center_y = y + template_height // 2
            button_centers.append((center_x, center_y))
    else:
        # 对于少量点，使用完整的非极大值抑制
        indices = []
        for i, rect1 in enumerate(rect_list):
            keep = True
            for j, rect2 in enumerate(rect_list):
                if i != j and is_overlapping(rect1, rect2) and score_list[j] > score_list[i]:
                    keep = False
                    break
            if keep:
                indices.append(i)
        
        # 计算匹配区域的中心点
        for idx in indices:
            x, y, x2, y2 = rect_list[idx]
            center_x = x + template_width // 2
            center_y = y + template_height // 2
            score = score_list[idx]
            # 只添加匹配度较高的点
            if score >= threshold:
                button_centers.append((center_x, center_y))
    
    # 如果使用了缩放，需要将中心点坐标调整回原始尺寸
    if scale_factor != 1.0:
        button_centers = [(int(x / scale_factor), int(y / scale_factor)) for x, y in button_centers]
    
    return button_centers

def is_overlapping(rect1, rect2, overlap_threshold=0.5):
    """检查两个矩形是否有足够的重叠"""
    x1, y1, x2, y2 = rect1
    a1, b1, a2, b2 = rect2
    
    # 计算交集区域
    intersection_x1 = max(x1, a1)
    intersection_y1 = max(y1, b1)
    intersection_x2 = min(x2, a2)
    intersection_y2 = min(y2, b2)
    
    if intersection_x1 >= intersection_x2 or intersection_y1 >= intersection_y2:
        return False  # 没有重叠
    
    # 计算重叠面积比例
    intersection_area = (intersection_x2 - intersection_x1) * (intersection_y2 - intersection_y1)
    rect1_area = (x2 - x1) * (y2 - y1)
    rect2_area = (a2 - a1) * (b2 - b1)
    min_area = min(rect1_area, rect2_area)
    
    return intersection_area / min_area > overlap_threshold

def simulate_click_sendinput(x, y, down_delay=0.01, up_delay=0.01):
    """
    使用SendInput API模拟鼠标点击，这是更底层的方法
    """
    try:
        # 计算归一化坐标 (0-65535范围)
        normalized_x = int(x * 65535 / screen_width)
        normalized_y = int(y * 65535 / screen_height)
        
        # 创建移动鼠标事件
        move = INPUT()
        move.type = INPUT_MOUSE
        move.u.mi.dx = normalized_x
        move.u.mi.dy = normalized_y
        move.u.mi.mouseData = 0
        move.u.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE
        move.u.mi.time = 0
        move.u.mi.dwExtraInfo = 0
        
        # 创建鼠标左键按下事件
        down = INPUT()
        down.type = INPUT_MOUSE
        down.u.mi.dx = normalized_x
        down.u.mi.dy = normalized_y
        down.u.mi.mouseData = 0
        down.u.mi.dwFlags = MOUSEEVENTF_LEFTDOWN
        down.u.mi.time = 0
        down.u.mi.dwExtraInfo = 0
        
        # 创建鼠标左键释放事件
        up = INPUT()
        up.type = INPUT_MOUSE
        up.u.mi.dx = normalized_x
        up.u.mi.dy = normalized_y
        up.u.mi.mouseData = 0
        up.u.mi.dwFlags = MOUSEEVENTF_LEFTUP
        up.u.mi.time = 0
        up.u.mi.dwExtraInfo = 0
        
        # 发送移动事件
        SendInput(1, ctypes.pointer(move), ctypes.sizeof(INPUT))
        
        # 发送按下事件
        SendInput(1, ctypes.pointer(down), ctypes.sizeof(INPUT))
        time.sleep(down_delay)
        
        # 发送释放事件
        SendInput(1, ctypes.pointer(up), ctypes.sizeof(INPUT))
        time.sleep(up_delay)
        
        return True
    except Exception as e:
        log_text.insert(tk.END, f"SendInput点击失败: {str(e)}\n")
        log_text.see(tk.END)
        return False

def force_click(x, y, down_delay=0.01, up_delay=0.01):
    """
    尝试直接点击，使用最快的方式
    """
    try:
        # 方法1: 使用win32api模拟鼠标点击
        win32api.SetCursorPos((x, y))
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
        time.sleep(down_delay)
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
        time.sleep(up_delay)
        return True
    except Exception as e:
        log_text.insert(tk.END, f"点击失败: {str(e)}\n")
        log_text.see(tk.END)
        return False

def click_buttons(button_centers, window_x, window_y, click_interval=0.01, down_delay=0.01, up_delay=0.01, clicks_per_button=1):
    """点击所有发现的按钮"""
    global running
    
    if not button_centers:
        return 0
    
    # 优化：预先计算所有坐标，避免循环中的计算
    global_coords = [(window_x + center_x, window_y + center_y) for center_x, center_y in button_centers]
    
    # 应用更高效的点击方法
    click_method_lower = click_method.lower()
    use_sendinput = click_method_lower == "sendinput"
    
    # 使用更紧凑的循环减少函数调用开销
    click_count = 0
    click_func = simulate_click_sendinput if use_sendinput else force_click
    
    # 优化多次点击逻辑
    for global_x, global_y in global_coords:
        # 跳过屏幕外的坐标
        if global_x < 0 or global_y < 0 or global_x >= screen_width or global_y >= screen_height:
            continue
            
        # 批量点击同一坐标
        for _ in range(clicks_per_button):
            if not running:
                return click_count
                
            click_func(global_x, global_y, down_delay, up_delay)
            click_count += 1
            
            # 只在真正需要时添加点击间隔
            if click_interval > 0.001:  # 忽略极小的间隔
                time.sleep(click_interval)
    
    return click_count

def save_debug_image(screenshot, button_centers, window_x, window_y):
    """保存带有标记的调试图像"""
    if not button_centers:
        return
    
    # 创建一个截图的副本
    img = Image.fromarray(np.array(screenshot))
    draw = ImageDraw.Draw(img)
    
    # 在每个按钮中心画一个红色圆圈
    for center_x, center_y in button_centers:
        draw.ellipse(
            [(center_x - 10, center_y - 10), (center_x + 10, center_y + 10)],
            outline="red",
            width=2
        )
    
    # 保存图像
    debug_dir = "debug_images"
    os.makedirs(debug_dir, exist_ok=True)
    timestamp = time.strftime("%Y%m%d_%H%M%S")
    img.save(os.path.join(debug_dir, f"debug_{timestamp}.png"))
    
    # 记录日志
    log_text.insert(tk.END, f"调试图像已保存: debug_{timestamp}.png\n")
    log_text.see(tk.END)

def stop_program():
    """停止程序运行"""
    global running
    running = False
    log_text.insert(tk.END, "正在停止程序...\n")
    log_text.see(tk.END)
    # 启用开始按钮，禁用停止按钮
    start_button.config(state=tk.NORMAL)
    stop_button.config(state=tk.DISABLED)

def select_template_file():
    """选择模板文件"""
    global template_path, template_paths
    file_path = filedialog.askopenfilename(
        title="选择按钮模板图片",
        filetypes=[("图片文件", "*.png;*.jpg;*.jpeg;*.bmp")]
    )
    if file_path:
        # 文件路径标准化为绝对路径
        file_path = os.path.abspath(file_path)
        
        # 设置为第一优先级模板
        template_path = file_path
        # 当用户选择了新模板时，将它设为最高优先级
        if file_path not in template_paths:
            template_paths.insert(0, file_path)
        else:
            # 如果已存在，移到最前面
            template_paths.remove(file_path)
            template_paths.insert(0, file_path)
        
        # 提示用户是否保存到templates目录
        if not file_path.startswith(templates_dir):
            if messagebox.askyesno("保存模板", f"是否将此模板保存到templates目录以便下次使用？\n原路径: {file_path}"):
                save_template_to_templates_dir(file_path)
        
        update_template_display()
        preview_template()

def save_template_to_templates_dir(source_path):
    """将模板保存到templates目录"""
    try:
        # 创建模板目录（如果不存在）
        os.makedirs(templates_dir, exist_ok=True)
        
        # 使用原文件名
        filename = os.path.basename(source_path)
        # 如果名称冲突，添加时间戳
        if os.path.exists(os.path.join(templates_dir, filename)):
            name, ext = os.path.splitext(filename)
            timestamp = time.strftime("%Y%m%d_%H%M%S")
            filename = f"{name}_{timestamp}{ext}"
        
        # 目标路径
        target_path = os.path.join(templates_dir, filename)
        
        # 复制文件
        shutil.copy2(source_path, target_path)
        
        # 更新当前模板路径
        global template_path, template_paths
        template_path = target_path
        
        # 更新模板列表
        if source_path in template_paths:
            template_paths.remove(source_path)
        template_paths.insert(0, target_path)
        
        update_template_display()
        
        log_text.insert(tk.END, f"已将模板保存到: {target_path}\n")
        log_text.see(tk.END)
        
        return target_path
    except Exception as e:
        messagebox.showerror("错误", f"保存模板到templates目录失败: {e}")
        return source_path

def update_template_display():
    """更新模板显示并保存配置"""
    # 显示当前模板优先级列表
    templates_text = "模板优先级: "
    for i, path in enumerate(template_paths):
        if os.path.exists(path):
            templates_text += f"{i+1}.{os.path.basename(path)} "
    
    template_label.config(text=templates_text)
    
    # 保存配置
    save_template_config()

def preview_template():
    """预览模板图片"""
    try:
        # 创建预览窗口
        preview_window = tk.Toplevel(root)
        preview_window.title("模板预览")
        preview_window.attributes("-topmost", True)
        
        # 显示优先级列表中的所有模板
        for i, path in enumerate(template_paths):
            if os.path.exists(path):
                try:
                    img = Image.open(path)
                    # 保持图片原始比例，但限制最大尺寸为200x200
                    img.thumbnail((200, 200))
                    img_tk = ImageTk.PhotoImage(img)
                    
                    # 创建模板预览框架
                    frame = ttk.Frame(preview_window)
                    frame.pack(padx=10, pady=10)
                    
                    # 显示优先级标签
                    ttk.Label(frame, text=f"优先级 {i+1}: {os.path.basename(path)}").pack()
                    
                    # 显示图片
                    img_label = ttk.Label(frame, image=img_tk)
                    img_label.image = img_tk  # 保持引用以防止垃圾回收
                    img_label.pack()
                    
                    # 显示图片尺寸
                    ttk.Label(frame, text=f"尺寸: {img.width}x{img.height}").pack()
                    
                except Exception as e:
                    ttk.Label(preview_window, text=f"无法加载模板 {path}: {e}").pack(padx=10, pady=10)
            else:
                ttk.Label(preview_window, text=f"模板文件不存在: {path}").pack(padx=10, pady=10)
    except Exception as e:
        messagebox.showerror("错误", f"无法创建预览窗口: {e}")

def refresh_windows_list():
    """刷新窗口列表"""
    global windows_info
    windows_info = get_process_windows()
    
    # 清空下拉列表
    window_list['values'] = []
    
    # 添加窗口到下拉列表
    window_values = []
    for i, (_, title, pid, process_name) in enumerate(windows_info):
        window_values.append(f"{process_name} - {title} (PID: {pid})")
    
    window_list['values'] = window_values
    
    if window_values:
        window_list.current(0)  # 设置默认选择第一项

def on_window_select(event):
    """处理窗口选择事件"""
    global selected_hwnd
    selection = window_list.current()
    if selection >= 0:  # 确保有选择
        selected_hwnd = windows_info[selection][0]
        status_label.config(text=f"已选择: {windows_info[selection][3]} - {windows_info[selection][1]}")

def find_snowflake_buttons_with_priority(screenshot, template_paths, threshold=0.8):
    """
    在截图中按照优先级顺序查找所有与模板匹配的按钮
    
    Args:
        screenshot: 游戏窗口的截图
        template_paths: 按优先级排序的模板图片路径列表
        threshold: 匹配阈值，值越高要求匹配越精确
        
    Returns:
        按钮中心点坐标列表 [(x, y), ...]
    """
    global last_successful_template
    
    # 快速路径：如果有上次成功的模板，优先使用它
    if last_successful_template and os.path.exists(last_successful_template):
        # 优化：仅在调试模式下记录日志，减少UI更新
        if debug_mode:
            log_text.insert(tk.END, f"使用上次成功的模板: {last_successful_template}\n")
            log_text.see(tk.END)
        
        # 尝试使用上次成功的模板进行快速匹配
        button_centers = find_snowflake_buttons(screenshot, last_successful_template, threshold)
        if button_centers:
            # 成功找到按钮，保持使用同一模板
            return button_centers, last_successful_template
        
        # 上次成功的模板失败，尝试使用其他模板
        if debug_mode:
            log_text.insert(tk.END, f"上次成功的模板没有找到按钮，尝试其他模板\n")
            log_text.see(tk.END)
        last_successful_template = None
    
    # 按优先级顺序尝试模板
    for template_path in template_paths:
        if not os.path.exists(template_path):
            continue
            
        button_centers = find_snowflake_buttons(screenshot, template_path, threshold)
        if button_centers:
            # 记住成功的模板，下次直接使用
            last_successful_template = template_path
            return button_centers, template_path
    
    # 所有模板都没有匹配
    return [], None

def auto_click_thread():
    """自动点击线程"""
    global running, debug_mode, log_update_count
    hwnd = selected_hwnd
    
    if not hwnd:
        messagebox.showerror("错误", "请先选择一个窗口")
        return
    
    # 获取参数值
    try:
        detection_threshold = float(threshold_entry.get())
        click_interval = float(click_interval_entry.get())
        scan_interval = float(scan_interval_entry.get())
        max_no_detection = int(max_no_detection_entry.get())
        down_delay = float(down_delay_entry.get())
        up_delay = float(up_delay_entry.get())
        clicks_per_button = int(clicks_per_button_entry.get())
    except ValueError:
        messagebox.showerror("错误", "请输入有效的参数值")
        return
    
    # 将窗口置于前台
    if not focus_window(hwnd):
        messagebox.showerror("错误", "无法将窗口置于前台")
        return
    
    # 主循环
    no_detection_count = 0
    total_clicks = 0
    
    log_text.delete(1.0, tk.END)
    log_text.insert(tk.END, "开始检测按钮...\n")
    log_text.see(tk.END)
    
    running = True
    start_button.config(state=tk.DISABLED)
    stop_button.config(state=tk.NORMAL)
    
    # 更高效的日志更新（减少UI操作带来的延迟）
    log_update_interval = 10  # 增大日志更新间隔
    last_log_time = time.time()
    log_update_min_interval = 0.5  # 最小日志更新间隔（秒）
    
    # 记录上次窗口信息，避免频繁获取
    last_window_info = None
    
    try:
        while running and no_detection_count < max_no_detection:
            loop_start_time = time.time()
            
            # 使用上次的窗口信息或获取新的窗口信息
            if last_window_info:
                window_info = last_window_info
            else:
                window_info = get_window_rect(hwnd)
                if window_info:
                    last_window_info = window_info
                else:
                    current_time = time.time()
                    if current_time - last_log_time > log_update_min_interval:
                        log_text.insert(tk.END, "无法获取窗口信息，停止程序\n")
                        log_text.see(tk.END)
                        last_log_time = current_time
                    break
            
            window_x, window_y, window_width, window_height = window_info
            
            # 优化截图过程 - 只获取窗口内容，不进行不必要的转换
            screenshot = pyautogui.screenshot(region=(window_x, window_y, window_width, window_height))
            
            # 按优先级检测按钮 - 使用优化后的优先级扫描
            button_centers, used_template = find_snowflake_buttons_with_priority(screenshot, template_paths, detection_threshold)
            
            # 调试模式时间戳 - 减少不必要的图片保存
            if debug_mode and button_centers:
                current_time = time.time()
                if current_time - last_log_time > 1.0:  # 限制调试图片保存频率
                    save_debug_image(screenshot, button_centers, window_x, window_y)
                    last_log_time = current_time
            
            # 点击发现的按钮 - 直接处理，避免额外检查
            if button_centers:
                clicks = click_buttons(button_centers, window_x, window_y, click_interval, down_delay, up_delay, clicks_per_button)
                total_clicks += clicks
                
                # 限制日志更新频率
                current_time = time.time()
                if log_update_count % log_update_interval == 0 and current_time - last_log_time > log_update_min_interval:
                    log_message = f"使用模板 {os.path.basename(used_template)} 发现并点击了 {len(button_centers)} 个按钮，总计: {total_clicks}\n"
                    log_text.insert(tk.END, log_message)
                    log_text.see(tk.END)
                    last_log_time = current_time
                
                no_detection_count = 0  # 重置计数器
            else:
                no_detection_count += 1
                
                # 限制日志更新频率
                current_time = time.time()
                if log_update_count % log_update_interval == 0 and current_time - last_log_time > log_update_min_interval:
                    log_message = f"未发现按钮，等待下一轮扫描... ({no_detection_count}/{max_no_detection})\n"
                    log_text.insert(tk.END, log_message)
                    log_text.see(tk.END)
                    last_log_time = current_time
            
            # 增加日志更新计数
            log_update_count += 1
            
            # 性能优化 - 如果整个循环执行得太快，可以添加一个非常小的延迟来防止CPU占用过高
            loop_time = time.time() - loop_start_time
            if scan_interval > 0 and loop_time < scan_interval:
                time.sleep(scan_interval - loop_time)
        
        if not running:
            log_text.insert(tk.END, "程序已被用户停止\n")
        else:
            log_text.insert(tk.END, f"完成！共点击了 {total_clicks} 个按钮。\n")
        log_text.see(tk.END)
        
    except Exception as e:
        log_text.insert(tk.END, f"发生错误: {e}\n")
        log_text.see(tk.END)
    finally:
        running = False
        start_button.config(state=tk.NORMAL)
        stop_button.config(state=tk.DISABLED)

def start_clicking():
    """开始点击"""
    global click_thread
    if click_thread and click_thread.is_alive():
        messagebox.showinfo("提示", "点击线程已经在运行")
        return
    
    click_thread = threading.Thread(target=auto_click_thread)
    click_thread.daemon = True
    click_thread.start()

def stop_clicking():
    """停止点击"""
    stop_program()

def toggle_debug_mode():
    """切换调试模式"""
    global debug_mode
    debug_mode = not debug_mode
    if debug_mode:
        debug_button.config(text="关闭调试模式")
        log_text.insert(tk.END, "调试模式已开启，将保存识别结果图片\n")
    else:
        debug_button.config(text="开启调试模式")
        log_text.insert(tk.END, "调试模式已关闭\n")
    log_text.see(tk.END)

def change_click_method():
    """切换点击方法"""
    global click_method
    if click_method == "pyautogui":
        click_method = "api"
        click_method_button.config(text="点击方式: Windows API")
    elif click_method == "api":
        click_method = "post_message"
        click_method_button.config(text="点击方式: PostMessage")
    elif click_method == "post_message":
        click_method = "sendinput"
        click_method_button.config(text="点击方式: SendInput")
    elif click_method == "sendinput":
        click_method = "force"
        click_method_button.config(text="点击方式: 强制模式")
    else:
        click_method = "pyautogui"
        click_method_button.config(text="点击方式: PyAutoGUI")
    
    log_text.insert(tk.END, f"点击方式已更改为: {click_method}\n")
    log_text.see(tk.END)

def toggle_topmost():
    """切换窗口置顶状态"""
    if root.attributes("-topmost"):
        root.attributes("-topmost", False)
        topmost_button.config(text="置顶窗口")
        log_text.insert(tk.END, "窗口置顶已关闭\n")
    else:
        root.attributes("-topmost", True)
        topmost_button.config(text="取消置顶")
        log_text.insert(tk.END, "窗口置顶已开启\n")
    log_text.see(tk.END)

def request_admin_privileges():
    """请求管理员权限"""
    if not is_admin():
        if messagebox.askyesno("权限请求", "某些点击功能需要管理员权限，是否以管理员身份重启程序？"):
            request_admin()
    else:
        messagebox.showinfo("提示", "程序已经拥有管理员权限")

def register_hotkey():
    """注册热键，确保在子线程中能够停止程序"""
    def hotkey_handler():
        global running
        running = False
        log_text.insert(tk.END, "检测到热键 Ctrl+Alt+X，正在停止程序...\n")
        log_text.see(tk.END)
        # 在主线程中更新UI
        root.after(0, lambda: start_button.config(state=tk.NORMAL))
        root.after(0, lambda: stop_button.config(state=tk.DISABLED))
    
    keyboard.add_hotkey('ctrl+alt+x', hotkey_handler)
    log_text.insert(tk.END, "热键注册成功: Ctrl+Alt+X (停止程序)\n")
    log_text.see(tk.END)

# 添加导入剪贴板图片的功能
def import_clipboard_image():
    """从剪贴板导入图像作为模板"""
    global template_paths
    
    try:
        # 从剪贴板获取图像
        clipboard_image = ImageGrab.grabclipboard()
        
        if clipboard_image is None:
            messagebox.showwarning("警告", "剪贴板中没有图像")
            return
            
        # 创建模板目录（如果不存在）
        os.makedirs(templates_dir, exist_ok=True)
        
        # 创建唯一的文件名
        timestamp = time.strftime("%Y%m%d_%H%M%S")
        template_filename = f"clipboard_{timestamp}.png"
        template_path = os.path.join(templates_dir, template_filename)
        
        # 保存图像
        clipboard_image.save(template_path)
        
        # 添加到模板列表的末尾（低优先级）
        template_paths.append(template_path)
        
        # 更新UI
        update_template_display()
        
        # 显示消息
        log_text.insert(tk.END, f"已从剪贴板添加模板: {template_filename}\n")
        log_text.see(tk.END)
        
        # 预览新添加的模板
        preview_template()
        
    except Exception as e:
        messagebox.showerror("错误", f"导入剪贴板图像失败: {e}")

def manage_templates():
    """管理模板优先级"""
    global template_paths
    
    # 创建管理窗口
    manage_window = tk.Toplevel(root)
    manage_window.title("模板优先级管理")
    manage_window.attributes("-topmost", True)
    manage_window.geometry("500x400")
    
    # 创建说明标签
    ttk.Label(manage_window, text="调整模板的优先级顺序 (上面的优先级更高)").pack(pady=10)
    
    # 创建列表框显示当前模板
    list_frame = ttk.Frame(manage_window)
    list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
    
    templates_listbox = tk.Listbox(list_frame, width=70, height=15)
    templates_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    
    # 添加滚动条
    scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=templates_listbox.yview)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    templates_listbox.config(yscrollcommand=scrollbar.set)
    
    # 填充列表
    def refresh_template_list():
        templates_listbox.delete(0, tk.END)
        for i, path in enumerate(template_paths):
            templates_listbox.insert(tk.END, f"{i+1}. {os.path.basename(path)} - {path}")
    
    refresh_template_list()
    
    # 按钮框架
    button_frame = ttk.Frame(manage_window)
    button_frame.pack(fill=tk.X, padx=10, pady=10)
    
    # 移动项目的函数
    def move_up():
        selected = templates_listbox.curselection()
        if not selected or selected[0] == 0:  # 已经是第一项
            return
            
        idx = selected[0]
        template_paths.insert(idx-1, template_paths.pop(idx))
        refresh_template_list()
        templates_listbox.selection_set(idx-1)
        update_template_display()
    
    def move_down():
        selected = templates_listbox.curselection()
        if not selected or selected[0] == len(template_paths) - 1:  # 已经是最后一项
            return
            
        idx = selected[0]
        template_paths.insert(idx+1, template_paths.pop(idx))
        refresh_template_list()
        templates_listbox.selection_set(idx+1)
        update_template_display()
    
    def move_to_top():
        selected = templates_listbox.curselection()
        if not selected or selected[0] == 0:  # 已经是第一项
            return
            
        idx = selected[0]
        template_path = template_paths.pop(idx)
        template_paths.insert(0, template_path)
        refresh_template_list()
        templates_listbox.selection_set(0)
        update_template_display()
    
    def move_to_bottom():
        selected = templates_listbox.curselection()
        if not selected or selected[0] == len(template_paths) - 1:  # 已经是最后一项
            return
            
        idx = selected[0]
        template_path = template_paths.pop(idx)
        template_paths.append(template_path)
        refresh_template_list()
        templates_listbox.selection_set(len(template_paths) - 1)
        update_template_display()
    
    def delete_template():
        selected = templates_listbox.curselection()
        if not selected:
            return
            
        idx = selected[0]
        if messagebox.askyesno("确认", f"确定要从列表中删除模板 {os.path.basename(template_paths[idx])} 吗?"):
            template_paths.pop(idx)
            refresh_template_list()
            update_template_display()
    
    def preview_selected_template():
        selected = templates_listbox.curselection()
        if not selected:
            return
            
        idx = selected[0]
        template_path = template_paths[idx]
        
        # 创建预览窗口
        preview_window = tk.Toplevel(manage_window)
        preview_window.title(f"预览: {os.path.basename(template_path)}")
        preview_window.attributes("-topmost", True)
        
        try:
            img = Image.open(template_path)
            # 保持原始比例但限制最大尺寸
            img.thumbnail((400, 400))
            img_tk = ImageTk.PhotoImage(img)
            
            # 显示图片
            img_label = ttk.Label(preview_window, image=img_tk)
            img_label.image = img_tk  # 保持引用
            img_label.pack(padx=10, pady=10)
            
            # 显示图片信息
            ttk.Label(preview_window, text=f"文件: {template_path}").pack()
            ttk.Label(preview_window, text=f"尺寸: {img.width}x{img.height}").pack()
            
        except Exception as e:
            ttk.Label(preview_window, text=f"无法加载图片: {e}").pack(padx=10, pady=10)
    
    # 添加操作按钮
    ttk.Button(button_frame, text="上移", command=move_up).pack(side=tk.LEFT, padx=5)
    ttk.Button(button_frame, text="下移", command=move_down).pack(side=tk.LEFT, padx=5)
    ttk.Button(button_frame, text="移到顶部", command=move_to_top).pack(side=tk.LEFT, padx=5)
    ttk.Button(button_frame, text="移到底部", command=move_to_bottom).pack(side=tk.LEFT, padx=5)
    ttk.Button(button_frame, text="删除", command=delete_template).pack(side=tk.LEFT, padx=5)
    ttk.Button(button_frame, text="预览", command=preview_selected_template).pack(side=tk.LEFT, padx=5)

    # 添加将模板保存到templates目录的功能
    def copy_to_templates():
        selected = templates_listbox.curselection()
        if not selected:
            return
            
        idx = selected[0]
        source_path = template_paths[idx]
        
        # 如果已经在templates目录，则跳过
        if source_path.startswith(templates_dir):
            messagebox.showinfo("提示", "此模板已位于templates目录")
            return
            
        # 复制模板到templates目录
        target_path = save_template_to_templates_dir(source_path)
        
        # 更新列表显示
        refresh_template_list()
        
        # 选中新项
        for i, path in enumerate(template_paths):
            if path == target_path:
                templates_listbox.selection_set(i)
                break
    
    # 添加复制到templates按钮
    ttk.Button(button_frame, text="保存到templates", command=copy_to_templates).pack(side=tk.LEFT, padx=5)

    # 在窗口关闭时保存配置
    def on_manage_window_close():
        save_template_config()
        manage_window.destroy()
    
    manage_window.protocol("WM_DELETE_WINDOW", on_manage_window_close)

# 添加保存和加载模板配置的函数
def save_template_config():
    """保存模板配置到文件"""
    try:
        config = {
            "template_paths": template_paths,
            "last_successful_template": last_successful_template
        }
        
        config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "template_config.json")
        with open(config_path, "w", encoding="utf-8") as f:
            json.dump(config, f, ensure_ascii=False, indent=2)
        
        if debug_mode:
            log_text.insert(tk.END, "模板配置已保存\n")
            log_text.see(tk.END)
    except Exception as e:
        log_text.insert(tk.END, f"保存模板配置失败: {e}\n")
        log_text.see(tk.END)

def load_template_config():
    """从文件加载模板配置"""
    global template_paths, last_successful_template
    
    try:
        config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "template_config.json")
        if os.path.exists(config_path):
            with open(config_path, "r", encoding="utf-8") as f:
                config = json.load(f)
                
            # 验证路径是否存在，过滤掉不存在的模板
            loaded_paths = config.get("template_paths", [])
            valid_paths = [path for path in loaded_paths if os.path.exists(path)]
            
            if valid_paths:
                template_paths = valid_paths
                
            # 加载上次成功的模板
            last_template = config.get("last_successful_template")
            if last_template and os.path.exists(last_template):
                last_successful_template = last_template
                
            # 只有在log_text已经定义时才记录日志
            if 'log_text' in globals():
                log_text.insert(tk.END, f"已加载 {len(valid_paths)} 个模板配置\n")
                log_text.see(tk.END)
            
            # 检查默认模板是否在路径中，如果不在则添加
            default_templates = ["dian.png", "dian2.png", "dian3.png", "dian4.png"]
            for default_template in default_templates:
                if default_template not in [os.path.basename(p) for p in template_paths]:
                    if os.path.exists(default_template):
                        template_paths.append(default_template)
                        if 'log_text' in globals():
                            log_text.insert(tk.END, f"添加默认模板: {default_template}\n")
    except Exception as e:
        if 'log_text' in globals():
            log_text.insert(tk.END, f"加载模板配置失败: {e}\n")
            log_text.see(tk.END)
        else:
            print(f"加载模板配置失败: {e}")

# 在程序退出时保存配置
def on_closing():
    """程序退出时的处理"""
    save_template_config()
    # 退出时清理热键
    keyboard.unhook_all()
    root.destroy()

# 添加功能，用户可以打开templates目录浏览所有模板
def open_templates_directory():
    """打开templates目录"""
    templates_dir_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
    os.makedirs(templates_dir_path, exist_ok=True)  # 确保目录存在
    
    # 使用系统默认的文件管理器打开目录
    try:
        if sys.platform == 'win32':
            os.startfile(templates_dir_path)
        elif sys.platform == 'darwin':  # macOS
            subprocess.call(['open', templates_dir_path])
        else:  # Linux
            subprocess.call(['xdg-open', templates_dir_path])
        
        log_text.insert(tk.END, f"已打开模板目录: {templates_dir_path}\n")
        log_text.see(tk.END)
    except Exception as e:
        messagebox.showerror("错误", f"无法打开模板目录: {e}")

# 程序入口点
if __name__ == "__main__":
    # 创建主窗口
    root = tk.Tk()
    root.title("自动点击工具 v3.0")
    root.geometry("800x600")
    
    # 设置程序图标
    try:
        if os.path.exists("icon.ico"):
            root.iconbitmap("icon.ico")
    except:
        pass
    
    # 创建左右分栏
    main_paned = ttk.PanedWindow(root, orient=tk.HORIZONTAL)
    main_paned.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
    
    # 左侧配置区域
    config_frame = ttk.Frame(main_paned)
    main_paned.add(config_frame, weight=1)
    
    # 窗口选择区域
    window_frame = ttk.LabelFrame(config_frame, text="窗口选择")
    window_frame.pack(fill=tk.X, padx=5, pady=5)
    
    window_list = ttk.Combobox(window_frame, width=50, state="readonly")
    window_list.pack(side=tk.LEFT, padx=5, pady=5)
    window_list.bind("<<ComboboxSelected>>", on_window_select)
    
    refresh_button = ttk.Button(window_frame, text="刷新", command=refresh_windows_list)
    refresh_button.pack(side=tk.LEFT, padx=5, pady=5)
    
    status_label = ttk.Label(window_frame, text="未选择窗口")
    status_label.pack(side=tk.LEFT, padx=5, pady=5)
    
    # 填充窗口列表
    refresh_windows_list()
    
    # 模板选择区域
    template_frame = ttk.LabelFrame(config_frame, text="模板设置")
    template_frame.pack(fill=tk.X, padx=5, pady=5)
    
    select_button = ttk.Button(template_frame, text="选择模板", command=select_template_file)
    select_button.pack(side=tk.LEFT, padx=5, pady=5)
    
    clipboard_button = ttk.Button(template_frame, text="从剪贴板添加(低优先级)", command=import_clipboard_image)
    clipboard_button.pack(side=tk.LEFT, padx=5, pady=5)
    
    manage_button = ttk.Button(template_frame, text="管理模板", command=manage_templates)
    manage_button.pack(side=tk.LEFT, padx=5, pady=5)
    
    open_dir_button = ttk.Button(template_frame, text="打开模板目录", command=open_templates_directory)
    open_dir_button.pack(side=tk.LEFT, padx=5, pady=5)
    
    preview_button = ttk.Button(template_frame, text="预览模板", command=preview_template)
    preview_button.pack(side=tk.LEFT, padx=5, pady=5)
    
    # 更新显示模板优先级
    templates_text = "模板优先级: "
    for i, path in enumerate(template_paths):
        if os.path.exists(path):
            templates_text += f"{i+1}.{os.path.basename(path)} "
    
    template_label = ttk.Label(template_frame, text=templates_text)
    template_label.pack(side=tk.LEFT, padx=5, pady=5)

    # 参数设置
    params_frame = ttk.LabelFrame(config_frame, text="参数设置")
    params_frame.pack(fill=tk.X, padx=5, pady=5)

    ttk.Label(params_frame, text="检测阈值 (0.1-1.0):").grid(row=0, column=0, sticky=tk.W, padx=5, pady=2)
    threshold_entry = ttk.Entry(params_frame, width=10)
    threshold_entry.insert(0, "0.8")  # 提高默认阈值，减少误检
    threshold_entry.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2)

    ttk.Label(params_frame, text="点击间隔 (秒):").grid(row=1, column=0, sticky=tk.W, padx=5, pady=2)
    click_interval_entry = ttk.Entry(params_frame, width=10)
    click_interval_entry.insert(0, "0.01")  # 减少默认间隔
    click_interval_entry.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2)

    ttk.Label(params_frame, text="扫描间隔 (秒):").grid(row=2, column=0, sticky=tk.W, padx=5, pady=2)
    scan_interval_entry = ttk.Entry(params_frame, width=10)
    scan_interval_entry.insert(0, "0")  # 默认无间隔持续扫描
    scan_interval_entry.grid(row=2, column=1, sticky=tk.W, padx=5, pady=2)

    ttk.Label(params_frame, text="最大无检测次数:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=2)
    max_no_detection_entry = ttk.Entry(params_frame, width=10)
    max_no_detection_entry.insert(0, "5")
    max_no_detection_entry.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2)

    # 添加新参数
    ttk.Label(params_frame, text="每个按钮点击次数:").grid(row=0, column=2, sticky=tk.W, padx=5, pady=2)
    clicks_per_button_entry = ttk.Entry(params_frame, width=10)
    clicks_per_button_entry.insert(0, "1")
    clicks_per_button_entry.grid(row=0, column=3, sticky=tk.W, padx=5, pady=2)

    ttk.Label(params_frame, text="鼠标按下延时 (秒):").grid(row=1, column=2, sticky=tk.W, padx=5, pady=2)
    down_delay_entry = ttk.Entry(params_frame, width=10)
    down_delay_entry.insert(0, "0.01")  # 减少默认延迟
    down_delay_entry.grid(row=1, column=3, sticky=tk.W, padx=5, pady=2)

    ttk.Label(params_frame, text="鼠标释放延时 (秒):").grid(row=2, column=2, sticky=tk.W, padx=5, pady=2)
    up_delay_entry = ttk.Entry(params_frame, width=10)
    up_delay_entry.insert(0, "0.01")  # 减少默认延迟
    up_delay_entry.grid(row=2, column=3, sticky=tk.W, padx=5, pady=2)

    # 右侧: 配置和控制区域
    control_frame = ttk.Frame(config_frame)
    control_frame.pack(fill=tk.X, padx=5, pady=10)

    start_button = ttk.Button(control_frame, text="开始点击", command=start_clicking)
    start_button.pack(side=tk.LEFT, padx=5)

    stop_button = ttk.Button(control_frame, text="停止点击", command=stop_clicking, state=tk.DISABLED)
    stop_button.pack(side=tk.LEFT, padx=5)

    debug_button = ttk.Button(control_frame, text="调试模式: 关闭", command=toggle_debug_mode)
    debug_button.pack(side=tk.LEFT, padx=5)

    click_method_button = ttk.Button(control_frame, text="点击方式: 强制模式", command=change_click_method)
    click_method_button.pack(side=tk.LEFT, padx=5)

    topmost_button = ttk.Button(control_frame, text="窗口置顶: 开启", command=toggle_topmost)
    topmost_button.pack(side=tk.LEFT, padx=5)
    
    admin_button = ttk.Button(control_frame, text="请求管理员权限", command=request_admin_privileges)
    admin_button.pack(side=tk.LEFT, padx=5)

    # 日志区域
    log_frame = ttk.LabelFrame(config_frame, text="运行日志", padding=5)
    log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

    log_text = tk.Text(log_frame, height=10, wrap=tk.WORD)
    log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

    scrollbar = ttk.Scrollbar(log_text, command=log_text.yview)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    log_text.configure(yscrollcommand=scrollbar.set)

    # 初始化窗口列表
    refresh_windows_list()

    # 注册热键处理
    register_hotkey()

    # 现在UI组件已创建，可以加载配置
    load_template_config()
    
    # 检查管理员权限
    if not is_admin():
        log_text.insert(tk.END, "提示: 程序未以管理员身份运行，某些点击功能可能受限\n")
        log_text.see(tk.END)
    
    # 设置关闭窗口的处理函数
    root.protocol("WM_DELETE_WINDOW", on_closing)
    
    # 启动主循环
    root.mainloop() 